﻿// Learn more about F# at http://fsharp.net
#nowarn "40"
#nowarn "62"

open System.Net
open System.Text
open Spread


type internal agentState = {
    MyGroup : string
    Channel : SpreadMessage -> unit }


type Input =                    // Input consists of...
    | Command of string         // a command from the console, or
    | Event of SpreadEvent      // an event from the spread daemon
    
// Execute a command
let private runCommand state cmd = 
    match String.split [' '] cmd with
    | "j" :: group :: tl ->
        Join group |> state.Channel
    | "l" :: group :: tl ->
        Leave group |> state.Channel
    | "s" :: group :: size :: tl ->
        let bytes = Array.create (Int32.of_string size) 0uy
        Data { Groups = [|group|]; ServiceType = MessageFlags.Causal; MessageType = 0us; Data = bytes} 
        |> state.Channel
    | "b" :: group :: size :: num :: tl ->
        let bytes = Array.create (Int32.of_string size) 0uy
        let data = Data { Groups = [|group|]; ServiceType = MessageFlags.Causal; MessageType = 1us; Data = bytes} 
        for i = 1 to Int32.of_string num do
            data |> state.Channel
    | "d" :: tl ->
        Disconnect |> state.Channel
    | "k" :: tl ->
        Kill |> state.Channel
    | _ -> ()

// Process an event from the spread daemon
let private processEvent state msg = 
    match msg with
    | Connection (group, chan) ->
        printfn "Connected. Private group is %s." group
        Some { state with MyGroup = group; Channel = chan}
    | Transition id ->
        printfn "Transition for %s. (%d,%d,%d)" id.Group id.Gid.ProcessId id.Gid.Timestamp id.Gid.Index
        Some state
    | MembershipChange change ->
        // TODO: Should really dump all the membership information, given i went to
        // all the trouble of parsing it off the wire.
        printfn "Membership change."
        Seq.length change.Members |> printfn "  Group: %s has %d members." change.Group
        Some state
    | SelfLeave group ->
        printfn "Left group %s." group
        Some state
    | Message msg when msg.MessageType = 0us ->
        printfn "Message: Received %d bytes from %s to %s " (Array.length msg.Data) msg.Sender (String.concat " " msg.Groups)
        Some state
    | Message msg ->
        Some state
    | Disconnection fault ->
        match fault with 
        | Some error -> printfn "Disconnected - %s." error.Message
        | None -> printfn "Disconnected."
        None

// Spawn our agent, passing the endPoint of the spread daemon
let private startAgent endPoint = 
    // This mailbox handles commands AND events
    let commands = MailboxProcessor.Start(fun inbox ->
        let rec loop state = 
            async {
                // printf "> "
                let! inp = inbox.Receive()
                match inp with
                | Command cmd ->
                    runCommand state cmd
                    do! loop state
                | Event evt ->
                    match processEvent state evt with
                    | Some state' ->
                        do! loop state'
                    | None -> ()
            }
        loop { MyGroup = ""; Channel = ignore })

    (fun evt -> Event evt |> commands.Post) 
        |> Spread.runClient endPoint true None

    commands

match System.Environment.GetCommandLineArgs() with
| [|_;ipaddr;port|] ->
    let ep = IPEndPoint(IPAddress.Parse(ipaddr), Int32.of_string(port))
    let cmds = startAgent ep
    let rec loop input = 
        match input with
        | "" ->
            cmds.Post(Command "k")
        | cmd -> 
            cmds.Post(Command cmd)
            System.Console.ReadLine() |> loop
    System.Console.ReadLine() |> loop
| _ ->
    printfn "Usage: FSpreadConsole <idaddr> <port>"